#include "nr_io_xml.h"

nr_io_xml::nr_io_xml(void)
{
    m_DeviceTypeFile = NR_DEVICE_TYPE_FILE;
    m_DeviceItemsFile = NR_DEVICE_ITEMS_FILE;
}


nr_io_xml::~nr_io_xml(void)
{
}

nr_error_t nr_io_xml::read_device_types( nr_device_types& device_types )
{
    m_device_types = &device_types;

    QString xml_path = nr_util::get_install_dir() + QString(m_DeviceTypeFile);
    QFile file(xml_path);
    if (!file.open(QIODevice::ReadOnly))
    {
        return NR_ERROR_OPEN_FILE_FAILED;
    }

    QString error_msg;
    int error_line = 0, error_column = 0; 
    QDomDocument doc;
    if (!doc.setContent(&file, &error_msg, &error_line, &error_column))
    {
        return NR_ERROR_PARSE_XML_FAILED;
    }

    QDomElement types_node = doc.namedItem("DeviceTypes").toElement();
    QDomNodeList type_list = types_node.elementsByTagName("DeviceType");

    // Parse each dom node
    for(int i = 0 ; i < type_list.count(); ++i)
    {
        nr_device_type *device_type = new nr_device_type;

        // Convert the device type node to device type
        bool ret = node_to_device_type(type_list.item(i).toElement(), *device_type);
        Q_ASSERT(ret);
        Q_ASSERT(!m_device_types->contains(device_type->id()));

        m_device_types->insert(device_type->id(), device_type);
    }

    // Load the module of each device type
    foreach(nr_device_type* device_type, device_types)
    {
        nr_error_t status = device_type->load_module();
        Q_ASSERT(NR_SUCCEED(status));
    }

    file.close();
    return NR_ERROR_NONE;
}

nr_error_t nr_io_xml::write_device_types( nr_device_types& device_types )
{
    m_device_types = &device_types;

    QString xml_path = nr_util::get_install_dir() + QString(m_DeviceTypeFile);
    QFile file(xml_path);
    if (!file.open(QIODevice::WriteOnly|QIODevice::Truncate))
    {
        return NR_ERROR_OPEN_FILE_FAILED;
    }

    QDomDocument doc;
    QDomText text;
    QDomElement element;
    QDomProcessingInstruction instruction;
    instruction = doc.createProcessingInstruction("xml","version=\"1.0\" encoding=\"utf-8\"");
    doc.appendChild(instruction);

    QDomElement root = doc.createElement("DeviceTypes");
    doc.appendChild(root);

    QHashIterator<nr_id_t, nr_device_type*> devicetypes_iterator( *m_device_types );
    while ( devicetypes_iterator.hasNext() ) {
        devicetypes_iterator.next();
        //devicetypes_iterator.key();//devicetypes_iterator.value();

        //add DeviceType one by one
        QDomElement devicetype_node = doc.createElement("DeviceType");
        create_node_to_devicetypes( doc , devicetype_node , *devicetypes_iterator.value() );

        root.appendChild(devicetype_node);
    }

    QTextStream out(&file);
    doc.save(out,4);
    file.close();
    return NR_ERROR_NONE;
}

nr_error_t nr_io_xml::read_device_items( nr_device_item_server& item_server )
{
    // Load from the xml current now, then should load from the database
    QString xml_path = nr_util::get_install_dir() + QString(m_DeviceItemsFile);
    QFile file(xml_path);
    if (!file.open(QIODevice::ReadOnly))
    {
        return NR_ERROR_OPEN_FILE_FAILED;
    }

    QString error_msg;
    int error_line = 0, error_column = 0; 
    QDomDocument doc;
    if (!doc.setContent(&file, &error_msg, &error_line, &error_column))
    {
        return NR_ERROR_PARSE_XML_FAILED;
    }

    QDomElement device_item_server_node = doc.namedItem("DeviceItemServer").toElement();
    parse_device_item_server_parameters(device_item_server_node, item_server);

    QDomNodeList device_group_list = device_item_server_node.childNodes();
    for(int i = 0 ; i < device_group_list.count(); ++i)
    {
        nr_device_group *device_group = new nr_device_group;
        bool ret = analyze_device_group_node(device_group_list.item(i).toElement(), *device_group, item_server);
        Q_ASSERT(ret);
        item_server.groups().insert(device_group->name(), device_group);
    }

    file.close();
    return NR_ERROR_NONE;
}

nr_error_t nr_io_xml::write_device_items( nr_device_item_server& item_server )
{
    QString xml_path = nr_util::get_install_dir() + QString(m_DeviceItemsFile);
    QFile file(xml_path);
    if (!file.open(QIODevice::WriteOnly|QIODevice::Truncate))
    {
        return NR_ERROR_OPEN_FILE_FAILED;
    }

    QDomDocument doc;
    QDomText text;
    QDomElement element;
    QDomProcessingInstruction instruction;
    instruction = doc.createProcessingInstruction("xml","version=\"1.0\" encoding=\"utf-8\"");
    doc.appendChild(instruction);

    create_node_device_item_server(doc, item_server);

    QTextStream out(&file);
    doc.save(out,4);
    file.close();
    return NR_ERROR_NONE;
}

nr_device_type* nr_io_xml::get_device_type( const nr_id_t &type_id )
{
    nr_device_types::iterator it = m_device_types->find(type_id);
    if (it != m_device_types->end())
    {
        return it.value();
    }
    else
    {
        Q_ASSERT(false);
        return NULL;
    }
}

bool nr_io_xml::item_node_parse(QDomElement &item_node, nr_device_type_property &item_device_type_property)
{
    QDomNamedNodeMap attrs = item_node.attributes();//parameter_attrs

    Q_ASSERT(attrs.contains("id"));
    item_device_type_property.id() = attrs.namedItem("id").nodeValue().toInt();

    Q_ASSERT(attrs.contains("name"));
    item_device_type_property.name() = attrs.namedItem("name").nodeValue();

    Q_ASSERT(attrs.contains("text"));
    item_device_type_property.text() = attrs.namedItem("text").nodeValue();

    if(attrs.contains("category"))
    {
        item_device_type_property.category() = attrs.namedItem("category").nodeValue();
    }

    if(attrs.contains("description"))
    {
        item_device_type_property.description() = attrs.namedItem("description").nodeValue();
    }

    if ((attrs.contains("default")))
    {
        //QVariant
        QString str = attrs.namedItem("default").nodeValue();
        item_device_type_property.default_value() = attrs.namedItem("default").nodeValue();
    }

    if (attrs.contains("value"))
    {
        QString str = attrs.namedItem("value").nodeValue();
        item_device_type_property.value() = attrs.namedItem("value").nodeValue();
    }

    if ((attrs.contains("type")))
    {
        // Parse the type
        QString str_type = attrs.namedItem("type").nodeValue();
        item_device_type_property.property_type() = nr_device_type_property::parse_property_type(str_type);
    }

    if ((attrs.contains("value_type")))
    {
        // Parse the value_type
        QString str_type = attrs.namedItem("value_type").nodeValue();
        item_device_type_property.value_type() = nr_object::parser_value_type(str_type);
    }

    if ((attrs.contains("extra_data")))
    {
        // Parse the extra_data in Parameters item
        item_device_type_property.extra_data() = attrs.namedItem("extra_data").nodeValue();
    }

    return true;
}

//deal with item node by recursively,item maybe contain items
bool nr_io_xml::items_node_to_property(QDomElement &items_node, nr_device_type_properties &item_device_type_propertys)
{
    QDomNodeList items_list = items_node.childNodes();//Item tags
    int items_list_count = items_list.count();
    for (int i = 0 ; i < items_list.count() ;i++)
    {
        Q_ASSERT( items_list.item(i).toElement().tagName()=="Item" );
        nr_device_type_property *device_type_item = new nr_device_type_property;
        item_node_parse(items_list.item(i).toElement(), *device_type_item);
        // Recursion
        items_node_to_property(items_list.item(i).toElement(), device_type_item->children());
        item_device_type_propertys.insert(device_type_item->id(), device_type_item);
    }
    return true;
}

bool nr_io_xml::node_to_device_type(QDomElement &type_node, nr_device_type &device_type)
{
    QDomNamedNodeMap attrs = type_node.attributes();

    // Parse the device type attribute
    Q_ASSERT(attrs.contains("id"));
    device_type.id() = attrs.namedItem("id").nodeValue().toInt();

    Q_ASSERT(attrs.contains("name"));
    device_type.name() = attrs.namedItem("name").nodeValue();

    Q_ASSERT(attrs.contains("text"));
    device_type.text() = attrs.namedItem("text").nodeValue();

    Q_ASSERT(attrs.contains("category"));
    device_type.category() = attrs.namedItem("category").nodeValue();

    Q_ASSERT(attrs.contains("description"));
    device_type.description() = attrs.namedItem("description").nodeValue();

    Q_ASSERT(attrs.contains("description"));
    device_type.module() = attrs.namedItem("module").nodeValue();

    // Parse sub-device types
    QDomNodeList sub_types_node = type_node.elementsByTagName("SubDeviceType");
    for(int i = 0; i < sub_types_node.count(); ++i)
    {
        QDomElement sub_type_node = sub_types_node.item(i).toElement();
        Q_ASSERT(sub_type_node.hasAttribute("id"));

        int sub_type_id = sub_type_node.attribute("id").toInt();
        Q_ASSERT(!device_type.child_types().contains(sub_type_id));
        device_type.child_types().insert(sub_type_id);
    }

    // Parse the properties
    QDomNode properties_node = type_node.namedItem("Properties");
    if (!properties_node.isNull())
    {
        bool ret = items_node_to_property(properties_node.toElement(), device_type.properties());
        Q_ASSERT(ret);
    }

    // Parse the parameters
    QDomNode parameters_node = type_node.namedItem("Parameters");
    if ( !parameters_node.isNull() )
    {
        bool ret = items_node_to_property(parameters_node.toElement(), device_type.parameters());
        Q_ASSERT(ret);
    }

    // Parser the events
    QDomNode events_node = type_node.namedItem("Events");
    if (!events_node.isNull())
    {
        bool ret = items_node_to_property(events_node.toElement(), device_type.events());
        Q_ASSERT(ret);
    }

    return true;
}


bool nr_io_xml::create_attributte_to_element(QDomDocument &doc, QDomElement &node, const QString &domattrname, QString &domattrvalue)
{
    QDomAttr domattr_para = doc.createAttribute(domattrname);
    domattr_para.setValue( domattrvalue );
    node.setAttributeNode( domattr_para );
    return true;
}

bool nr_io_xml::create_item_node(QDomDocument &doc, QDomElement &item_node, nr_device_type_property &item_property)
{
    create_attributte_to_element(doc, item_node, "id", QString::number(item_property.id()) );
    create_attributte_to_element(doc, item_node, "name", QString(item_property.name()) );
    create_attributte_to_element(doc, item_node, "text", QString(item_property.text()) );
    if ( QString(item_property.category()) != "" )
        create_attributte_to_element(doc, item_node, "category", QString(item_property.category()) );
    if ( QString(item_property.description()) != "" )
        create_attributte_to_element(doc, item_node, "description", QString(item_property.description()) );
    if ( QString(item_property.value().toString()) != "" )
        create_attributte_to_element(doc, item_node, "value", QString(item_property.value().toString()) );
    if ( QString(item_property.default_value().toString()) != "" )
        create_attributte_to_element(doc, item_node, "default", QString(item_property.default_value().toString()) );
    //property_type
    if ( QString(item_property.toString_property_type()) != "" && item_property.property_type() != nr_device_type_property::property_type_t(0) )
        create_attributte_to_element(doc, item_node, "type", QString(item_property.toString_property_type()) );
    //value_type
    if ( QString(item_property.toString_value_type()) != "" && item_property.value_type() != nr_object::value_type_t(0))
        create_attributte_to_element(doc, item_node, "value_type", QString(item_property.toString_value_type()) );
    if ( QString(item_property.extra_data()) != "" )
        create_attributte_to_element(doc, item_node, "extra_data", QString(item_property.extra_data()) );
    return true;
}

bool nr_io_xml::create_nodes_to_items(QDomDocument &doc, QDomElement &nodes, nr_device_type_properties &item_device_type_propertys)
{
    QHashIterator<nr_id_t, nr_device_type_property*> devicetypes_iterator( item_device_type_propertys );
    while ( devicetypes_iterator.hasNext() ) {
        devicetypes_iterator.next();
        //devicetypes_iterator.key();
        //devicetypes_iterator.value();

        QDomElement item_node = doc.createElement("Item");
        //createattributte_to_element( doc , item_node , *devicetypes_iterator.value() );
        create_item_node(doc, item_node, *devicetypes_iterator.value() );
        if ( !devicetypes_iterator.value()->children().isEmpty() )
        {
            create_nodes_to_items(doc, item_node, devicetypes_iterator.value()->children() );
        }
        nodes.appendChild(item_node);
    }

    return true;
}



bool nr_io_xml::create_node_to_devicetypes(QDomDocument &doc, QDomElement &devicetype_node, nr_device_type &device_type)
{
    //create text of DeviceType
    create_attributte_to_element(doc, devicetype_node, "id", QString::number(device_type.id()) );
    create_attributte_to_element(doc, devicetype_node, "name", QString(device_type.name()) );
    create_attributte_to_element(doc, devicetype_node, "text", QString(device_type.text()) );
    create_attributte_to_element(doc, devicetype_node, "category", QString(device_type.category()) );
    create_attributte_to_element(doc, devicetype_node, "description", QString(device_type.description()) );
    if ( QString(device_type.module()) != "" ) {
        create_attributte_to_element(doc, devicetype_node, "module", QString(device_type.module()) );
    }

    //create SubDeviceType node to DeviceType
    QSet<nr_id_t>::iterator subdeviceid_iterator;
    subdeviceid_iterator = device_type.child_types().begin();
    for ( ; subdeviceid_iterator != device_type.child_types().end() ; ++subdeviceid_iterator )
    {
        QDomElement subdevicetype_node = doc.createElement("SubDeviceType");
        create_attributte_to_element(doc, subdevicetype_node, "id", QString::number(*subdeviceid_iterator) );
        devicetype_node.appendChild( subdevicetype_node );
    }

    //create Properties node to DeviceType
    if ( !device_type.properties().isEmpty() )
    {
        QDomElement properties_node = doc.createElement("Properties");
        //call recurrence
        create_nodes_to_items(doc, properties_node, device_type.properties() );
        devicetype_node.appendChild( properties_node );
    }

    //create Parameters node to DeviceType
    if ( !device_type.parameters().isEmpty() )
    {
        QDomElement parameters_node = doc.createElement("Parameters");
        //call recurrence
        create_nodes_to_items(doc, parameters_node, device_type.parameters() );
        devicetype_node.appendChild( parameters_node );
    }

    //create Events node to DeviceType
    if ( !device_type.events().isEmpty() )
    {
        QDomElement events_node = doc.createElement("Events");
        //call recurrence
        create_nodes_to_items(doc, events_node, device_type.events() );
        devicetype_node.appendChild( events_node );
    }

    return true;
}

bool nr_io_xml::parse_device_item_server_parameters(QDomElement &node, nr_device_item_server &device_item_server)
{
    QDomNamedNodeMap attrs = node.attributes();

    Q_ASSERT(attrs.contains("id"));
    device_item_server.id() = attrs.namedItem("id").nodeValue();

    Q_ASSERT(attrs.contains("name"));
    device_item_server.name() = attrs.namedItem("name").nodeValue();

    Q_ASSERT(attrs.contains("type_id"));
    device_item_server.device_type() = this->get_device_type(
        attrs.namedItem("type_id").nodeValue().toInt());

    return true;
}

bool nr_io_xml::parse_device_group_parameters(QDomElement &node, nr_device_group &device_group)
{
    QDomNamedNodeMap attrs = node.attributes();

    Q_ASSERT(attrs.contains("name"));
    device_group.name() = attrs.namedItem("name").nodeValue();

    return true;
}

bool nr_io_xml::parse_device_item_paramaters(QDomElement &node, nr_device_item &device_item)
{
    QDomNamedNodeMap attrs = node.attributes();

    Q_ASSERT(attrs.contains("id"));
    device_item.id() = attrs.namedItem("id").nodeValue();

    Q_ASSERT(attrs.contains("name"));
    device_item.name() = attrs.namedItem("name").nodeValue();

    Q_ASSERT(attrs.contains("type_id"));
    device_item.device_type() = this->get_device_type(
        attrs.namedItem("type_id").nodeValue().toInt());

    return true;
}

bool nr_io_xml::parse_property_parameters(QDomElement &node, nr_device_property &device_property)
{
    QDomNamedNodeMap attrs = node.attributes();

    Q_ASSERT(attrs.contains("type_id"));
    device_property.id() = attrs.namedItem("type_id").nodeValue().toInt();
    device_property.property_type() = device_property.parse_property_type( node.tagName() );
    if ( device_property.property_type() == nr_device_property::Property)//Property
    {
        //QString text=node.toElement().text();
        device_property.value() = node.toElement().text();
    }

    return true;
}

bool nr_io_xml::analyze_property_node(QDomElement &property_node, nr_device_property &device_property)
{
    parse_property_parameters(property_node, device_property);

    QDomNode node = property_node.firstChild();
    while ( !node.isNull() )
    {
        QDomElement element = node.toElement();
        if ( element.tagName() == "PropertyGroup" || element.tagName() == "Property")
        {
            nr_device_property *new_device_property = new nr_device_property;
            analyze_property_node(element, *new_device_property);//recurrence
            device_property.children().insert(new_device_property->id(), new_device_property);
        }
        node = node.nextSibling();
    }

    return true;
}

bool nr_io_xml::analyze_device_item_node(QDomElement &device_item_node, nr_device_item &device_item)
{
    parse_device_item_paramaters(device_item_node, device_item);

    QDomNode node = device_item_node.firstChild();
    while ( !node.isNull() )
    {
        QDomElement element = node.toElement();
        if ( element.tagName() == "PropertyGroup" || element.tagName() == "Property")
        {
            nr_device_property *new_device_property = new nr_device_property;
            analyze_property_node(element, *new_device_property);
            device_item.properties().insert(new_device_property->id(), new_device_property);
        }
        else if ( element.tagName() == "DeviceItem" )
        {
            nr_device_item *new_device_item = new nr_device_item;
            analyze_device_item_node(element, *new_device_item);//recurrence
            new_device_item->parent_item() = &device_item;//add parent_item
            device_item.items().insert(new_device_item->id(), new_device_item);
        }

        node = node.nextSibling();
    }

    return true;
}

bool nr_io_xml::analyze_device_group_node(QDomElement &device_group_node, nr_device_group &device_group, nr_device_item &parent_device_item)
{
    parse_device_group_parameters(device_group_node, device_group);

    QDomNode node = device_group_node.firstChild();
    while ( !node.isNull() )
    {
        QDomElement element = node.toElement();
        if ( element.tagName() == "DeviceItem" )
        {
            nr_device_item *new_device_item = new nr_device_item;
            analyze_device_item_node(element, *new_device_item);
            new_device_item->parent_item() = &parent_device_item;//parent_item
            device_group.items().insert(new_device_item->id(), new_device_item);
        }
        else if ( element.tagName() == "DeviceGroup" )
        {
            nr_device_group *new_device_group = new nr_device_group;
            analyze_device_group_node(element, *new_device_group, parent_device_item);//recurrence
            device_group.groups().insert(new_device_group->name(), new_device_group);
        }

        node = node.nextSibling();
    }

    return true;
}

// bool nr_io_xml::create_attributte_to_element(QDomDocument &doc, QDomElement &node, const QString &attr_name, QString &attr_value)
// {
//     QDomAttr domattr_para = doc.createAttribute(attr_name);
//     domattr_para.setValue( attr_value );
//     node.setAttributeNode( domattr_para );
//     return true;
// }

//create node with PropertyGroup tag and Property tag
bool nr_io_xml::create_node_property(QDomDocument &doc, QDomElement &node, nr_device_property &device_property)
{
    create_attributte_to_element(doc, node, "type_id", QString::number(device_property.id()));
    QString nodeText_str = device_property.value().toString();
    if ( nodeText_str != "" )
    {
        QDomText node_text = doc.createTextNode( nodeText_str );
        node.appendChild( node_text );
    }

    foreach(nr_device_property* prop, device_property.children())
    {
        QString tag_name = prop->property_type_to_string();
        QDomElement property_node = doc.createElement(tag_name);
        create_node_property(doc, property_node, *prop);

        node.appendChild(property_node);
    }

    return true;
}

bool nr_io_xml::create_node_device_item(QDomDocument &doc, QDomElement &node, nr_device_item &device_item)
{
    create_attributte_to_element(doc, node, "id", device_item.id());
    create_attributte_to_element(doc, node, "name", device_item.name());
    create_attributte_to_element(doc, node, "type_id", QString::number(device_item.device_type()->id()));

    //QHashIterator<nr_id_t, nr_device_property*> device_properties_iterator( device_item.properties() );

    foreach(nr_device_property* prop, device_item.properties())
    {
        QString tagName = prop->property_type_to_string();
        QDomElement property_node =  doc.createElement( tagName );
        create_node_property(doc, property_node, *prop/**device_properties_iterator.value()*/);

        node.appendChild(property_node);
    }

    foreach(nr_device_item* item, device_item.items())
    {
        QDomElement device_item_node = doc.createElement("DeviceItem");
        create_node_device_item(doc, device_item_node, *item);
        node.appendChild(device_item_node);
    }

    return true;
}

bool nr_io_xml::create_node_device_group(QDomDocument &doc, QDomElement &node, nr_device_group &device_group)
{
    create_attributte_to_element(doc, node, "name", device_group.name());

    foreach(nr_device_group* group, device_group.groups())
    {

        QDomElement device_group_node = doc.createElement("DeviceGroup");
        create_node_device_group(doc, device_group_node, *group);
        node.appendChild(device_group_node);
    }

    foreach(nr_device_item* item, device_group.items())
    {
        QDomElement device_item_node = doc.createElement("DeviceItem");
        create_node_device_item(doc, device_item_node, *item);

        node.appendChild(device_item_node);
    }

    return true;
}

bool nr_io_xml::create_node_device_item_server(QDomDocument &doc,nr_device_item_server &device_item_server)
{
    QDomElement root = doc.createElement("DeviceItemServer");

    create_attributte_to_element(doc, root, "id", device_item_server.id());
    create_attributte_to_element(doc, root, "name", device_item_server.name());
    create_attributte_to_element(doc, root, "type_id", QString::number(device_item_server.device_type()->id()) );

    foreach(nr_device_group* device_group, device_item_server.groups())
    {
        QDomElement deviceGroup_node = doc.createElement("DeviceGroup");
        create_node_device_group(doc, deviceGroup_node, *device_group);

        root.appendChild(deviceGroup_node);
    }

    doc.appendChild(root);
    return true;
}